# Copyright (c) 2009, Mikael Arvids
# All rights reserved.

import re
import sys

#==============================================================================#
class Template:   
    #--------------------------------------------------------------------------#
    def __init__(self, filename):
        self.parser = _TemplateParser(filename)

        # Handle extensions
        if self.parser.extension is not None:
            extParser = _TemplateParser(self.parser.extension.process())

            # Replace all previous sections with the extended template sections
            self.parser.root = extParser.root

            # Add any additional blocks
            for block in extParser.blocks:
                if block not in self.parser.blocks:
                    self.parser.blocks[block] = extParser.blocks[block]

    #--------------------------------------------------------------------------#
    def process(self, context=locals()):
        return self.parser.process(context)

#==============================================================================#
class _TemplateParser:
    #--------------------------------------------------------------------------#
    def __init__(self, filename):
        self.regex_instr = re.compile('(.*?)\{\s*(\{|%|#|\*)\s*(.+?)\s*(\}|%|#|\*)\s*\}',
            re.DOTALL | re.MULTILINE)
        self.regex_statement = re.compile('([a-z]*)[\s\-]*(.*)')
        self.regex_endOfComment = re.compile('#\s*\}')

        # Create the input data
        self.filename = filename
        inputFile = open(filename, 'r')
        self.input = ''.join(inputFile.readlines())
        inputFile.close()

        # Initialize the parser state
        self.root = _Section()
        self.currentSection = self.root
        self.blocks = {}
        self.extension = None
        self.index = 0
        
        # Parse
        self._parse()
        
        # Make sure that all sections are properly closed
        if self.currentSection != self.root:
            raise Exception(self.error(
                'EOF reached in section', self.currentSection.type))

    #--------------------------------------------------------------------------#
    def _parse(self):
        while len(self.input[self.index:]) > 0:
            # Find the next instruction
            match = self.regex_instr.match(self.input[self.index:])
            if match is not None:
                text = _Section_text()
                text.handle(match.group(1), self)
                next_index = self.index + match.end()
                trim = False
                
                # Check the instruction type
                if match.group(2) == '{':
                    if match.group(4) != '}':
                        raise Exception(
                            self.error('Unmatched instruction type specifier'))
                    type = 'expr'
                    expr = match.group(3)

                elif match.group(2) == '*':
                    if match.group(4) != '*':
                        raise Exception(
                            self.error('Unmatched instruction type specifier'))
                    type = 'exec'
                    expr = match.group(3)
                    trim = True
                            
                elif match.group(2) == '%':
                    if match.group(4) != '%':
                        raise Exception(
                            self.error('Unmatched instruction type specifier'))

                    statement = self.regex_statement.match(match.group(3))
                    if statement is None:
                        raise Exception(
                            self.error('Unhandled statement', match.group(3)))

                    type = statement.group(1)
                    expr = statement.group(2)
                    trim = True

                elif match.group(2) == '#':
                    type = 'comment'
                    expr = None
                    
                    endOfComment = self.regex_endOfComment.search(
                        self.input[self.index+match.start():])
                    if endOfComment is None:
                        raise Exception(
                            self.error('Comment not closed'))
                            
                    next_index = self.index + endOfComment.end()
                    trim = True

                else:
                    raise Exception(
                            self.error('Unknown instruction', type))                    

                # Remove the newlines from the end of the processing blocks
                if trim:
                    if next_index < len(self.input):
                        if self.input[next_index] == '\n':
                            next_index += 1

                self.index = next_index

                # find a class that handles this instruction
                if globals().has_key('_Section_' + type):
                    section = globals()['_Section_' + type]()
                    section.handle(expr, self)
                else:
                    raise Exception(self.error('Unhandled instruction', type))

            else:
                text = _Section_text()
                text.handle(self.input[self.index:], self)
                break

    #--------------------------------------------------------------------------#
    def process(self, context):
        return self.root.process(context, self.blocks)

    #--------------------------------------------------------------------------#
    def error(self, text, item=None):
        line = self.getCurrentLine()
        
        errorStr = str(sys.exc_info()[1]) + '\n'
        errorStr += text

        if item is not None:
            errorStr += ': \"' + item + '\"'
        errorStr += ' on line ' + str(line) + ' in ' + self.filename
        return errorStr
        
    #--------------------------------------------------------------------------#
    def getCurrentLine(self):
        return self.input[:self.index:].count('\n') + 1

#==============================================================================#
class _Section:
    type = 'None'
    #--------------------------------------------------------------------------#
    def __init__(self):
        self.sections = []
        self.parent = None
        self.line = None
        self.filename = None

    #--------------------------------------------------------------------------#
    def handle(self, expr, parser):
        self.line = parser.getCurrentLine()
        self.filename = parser.filename

    #--------------------------------------------------------------------------#
    def process(self, context, blocks):
        output = ''
        for section in self.sections:
            output += section.process(context, blocks)
        return output

    #--------------------------------------------------------------------------#
    def addSection(self, section):
        self.sections.append(section)
        section.parent = self
        
    #--------------------------------------------------------------------------#
    def error(self, type, item=None):
        errorStr = str(sys.exc_info()[1]) + '\n'
        errorStr += type
        if item is not None:
            errorStr += ': \"' + item + '\"'
        errorStr += ', on line ' + str(self.line) + ' in ' + self.filename
        return errorStr
        
#==============================================================================#
class _Section_text(_Section):
    type = 'text'

    #--------------------------------------------------------------------------#
    def handle(self, expr, parser):
        _Section.handle(self, expr, parser)
        if len(expr) > 0:
            self.text = expr
            parser.currentSection.addSection(self)
        
    #--------------------------------------------------------------------------#
    def process(self, context, blocks):
        return self.text

#==============================================================================#
class _Section_expr(_Section):
    type = 'expr'

    #--------------------------------------------------------------------------#
    def handle(self, expr, parser):
        _Section.handle(self, expr, parser)
        self.expr = expr
        parser.currentSection.addSection(self)

    #--------------------------------------------------------------------------#
    def process(self, context, blocks):
        try:
            return str(eval(self.expr, globals(), context))
        except:
            raise Exception(self.error(
                'Expression \"' + self.expr + '\" could not be evaluated'))

#==============================================================================#
class _Section_exec(_Section):
    type = 'exec'

    #--------------------------------------------------------------------------#
    def handle(self, expr, parser):
        _Section.handle(self, expr, parser)
        self.expr = expr
        parser.currentSection.addSection(self)

    #--------------------------------------------------------------------------#
    def process(self, context, blocks):
        try:
            exec(self.expr, globals(), context)
            return ''
        except:
            raise Exception(self.error(
                'Expression \"' + self.expr + '\" could not be executed'))

#==============================================================================#
class _Section_if(_Section):
    type = 'if'

    #--------------------------------------------------------------------------#
    def __init__(self):
        _Section.__init__(self)
        self.else_statement = None

    #--------------------------------------------------------------------------#
    def handle(self, expr, parser):
        _Section.handle(self, expr, parser)
        self.condition = expr
        parser.currentSection.addSection(self)
        parser.currentSection = self

    #--------------------------------------------------------------------------#
    def process(self, context, blocks):
        output = ''
        try:
            condition = eval(self.condition, globals(), context)
        except:
            raise Exception(self.error(
                'Condition \"' + self.condition + '\" could not be evaluated'))
        if condition:
            for section in self.sections:
                output += section.process(context, blocks)
        elif self.else_statement is not None:
            output += self.else_statement.process(context, blocks)
        return output

#==============================================================================#
class _Section_elif(_Section_if):
    type = 'if'

    #--------------------------------------------------------------------------#
    def handle(self, expr, parser):
        _Section.handle(self, expr, parser)
        self.condition = expr
        currentType = parser.currentSection.type
        if currentType != 'if' and currentType != 'elif':
            raise Exception(self.error('Invalid elif instruction'))

        parser.currentSection.else_statement = self
        self.parent = parser.currentSection.parent
        parser.currentSection = self

#==============================================================================#
class _Section_else(_Section):
    type = 'if'

    #--------------------------------------------------------------------------#
    def handle(self, expr, parser):
        _Section.handle(self, expr, parser)
        currentType = parser.currentSection.type
        if currentType != 'if' and currentType != 'elif':
            raise Exception(self.error('Invalid else instruction'))

        parser.currentSection.else_statement = self
        self.parent = parser.currentSection.parent
        parser.currentSection = self

#==============================================================================#
class _Section_for(_Section):
    type = 'for'
    regex_statement = re.compile('(.+?)\s*in\s*(.+)')

    #--------------------------------------------------------------------------#
    def handle(self, expr, parser):
        _Section.handle(self, expr, parser)
        self.statement = expr
        parser.currentSection.addSection(self)
        parser.currentSection = self

    #--------------------------------------------------------------------------#
    def process(self, context, blocks):
        output = ''
        match = self.regex_statement.match(self.statement)
        try:
            list = eval(match.group(2), globals(), context)
        except:
            raise Exception(self.error(
                'Name \"' + match.group(2) + '\" could not be resolved'))
            
        for element in list:
            sub_context = context
            sub_context[match.group(1)] = element
            for section in self.sections:
                output += section.process(sub_context, blocks)
        return output

#==============================================================================#
class _Section_block(_Section):
    type = 'block'

    #--------------------------------------------------------------------------#
    def handle(self, expr, parser):
        _Section.handle(self, expr, parser)
        self.name = expr
        parser.currentSection.addSection(self)
        parser.currentSection = self
        
        if parser.blocks.has_key(expr):
            raise Exception(self.error('Block \"' + expr + '\" already exist'))
        parser.blocks[expr] = self

    #--------------------------------------------------------------------------#
    def process(self, context, blocks):
        if self.name in blocks:
            sections = blocks[self.name].sections
        else:
            sections = self.sections
        output = ''
        for section in blocks[self.name].sections:
            output += section.process(context, blocks)
        return output

#==============================================================================#
class _Section_extends(_Section):
    type = 'extends'

    #--------------------------------------------------------------------------#
    def handle(self, expr, parser):
        _Section.handle(self, expr, parser)
        self.fileName = expr
        if parser.extension is None:
            parser.extension = self
        else:
            raise Exception(self.error('Only one extends instruction allowed'))

    #--------------------------------------------------------------------------#
    def process(self):
        return self.fileName.replace('\"', '')

#==============================================================================#
class _Section_include(_Section):
    type = 'include'

    #--------------------------------------------------------------------------#
    def handle(self, expr, parser):
        _Section.handle(self, expr, parser)
        self.fileName = expr
        includeParser = _TemplateParser(self.process())
        parser.currentSection.addSection(includeParser.root)

        for block in includeParser.blocks:
            if block not in parser.blocks:
                parser.blocks[block] = includeParser.blocks[block]

    #--------------------------------------------------------------------------#
    def process(self):
        return self.fileName.replace('\"', '')

#==============================================================================#
class _Section_end(_Section):
    type = 'end'

    #--------------------------------------------------------------------------#
    def handle(self, expr, parser):
        _Section.handle(self, expr, parser)
        type = expr.split()[0]
        if type != parser.currentSection.type:
            raise Exception(parser.error('Unbalanced end instruction: ', 
                'end-' + type))
        parser.currentSection = parser.currentSection.parent

#==============================================================================#
class _Section_comment(_Section):
    type = 'comment'

    #--------------------------------------------------------------------------#
    def handle(self, expr, parser):
        _Section.handle(self, expr, parser)
        
#==============================================================================#
if __name__ == '__main__':
    template = Template('example.template')
    context = {'var1': 'hej'}
    print template.process(context)
